/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Driver Module                                  */
/*                                                                           */
/*    Copyright (C) 2011 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    I2C Multiple Interface Software Driver                                 */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Includes
#include <drvI2C.h>
#include <krnlSystemTimer.h>
#include "drvIOConfig.h"

///////////////////////////////////////////////////////////////////////////////
// Types
#define drvSwI2C_PIN_STATE_LOW 0
#define drvSwI2C_PIN_STATE_OPEN 1

typedef void (*drvSwI2CClearPinFunc)(void);
typedef dosByte (*drvSwI2CReadPinFunc)(void);
typedef void (*drvSwI2CHalfBitDelayFunc)(void);

typedef struct 
{
	drvSwI2CClearPinFunc ClearSCL;
	drvSwI2CReadPinFunc ReadSCL;
	
	drvSwI2CClearPinFunc ClearSDA;
	drvSwI2CReadPinFunc ReadSDA;

	drvSwI2CHalfBitDelayFunc HalfBitDelay;
	
} drvSwI2CSettings;

///////////////////////////////////////////////////////////////////////////////
// Macros
#define I2C_MULTIPLE_INTERFACES_ENABLED

#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
#define drvSwI2CClearSCL settings->ClearSCL
#define drvSwI2CReadSCL settings->ReadSCL
#define drvSwI2CClearSDA settings->ClearSDA
#define drvSwI2CReadSDA settings->ReadSDA
#define drvSwI2CHalfBitDelay settings->HalfBitDelay
#endif

/*extern*/ drvSwI2CSettings g_drvSwI2CSettings[1];

#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
dosBool drvSwI2CStartFunc(dosByte in_interface_index, drvI2CRWMode in_mode, dosByte in_address);
dosBool drvSwI2CRestartFunc(dosByte in_interface_index, drvI2CRWMode in_mode, dosByte in_address);
void drvSwI2CStopFunc(dosByte in_interface_index);
dosBool drvSwI2CWriteByteFunc(dosByte in_interface_index, dosByte in_data);
dosByte drvSwI2CReadByteFunc(dosByte in_interface_index, drvI2CACK in_ack);
#else
dosBool drvSwI2CStartFunc(drvI2CRWMode in_mode, dosByte in_address);
dosBool drvSwI2CRestartFunc(drvI2CRWMode in_mode, dosByte in_address);
void drvSwI2CStopFunc(void);
dosBool drvSwI2CWriteByteFunc(dosByte in_data);
dosByte drvSwI2CReadByteFunc(drvI2CACK in_ack);
#endif

#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
#define drvSwI2CStart(a,b,c) drvSwI2CStartFunc(a,b,c)
#define drvSwI2CRestart(a,b,c) drvSwI2CRestartFunc(a,b,c)
#define drvSwI2CStop(a) drvSwI2CStopFunc(a)
#define drvSwI2CWriteByte(a,b) drvSwI2CWriteByteFunc(a,b)
#define drvSwI2CReadByte(a,b) drvSwI2CReadByteFunc(a,b)
#else
#define drvSwI2CStart(a,b,c) drvSwI2CStartFunc(b,c)
#define drvSwI2CRestart(a,b,c) drvSwI2CRestartFunc(b,c)
#define drvSwI2CStop(a) drvSwI2CStopFunc()
#define drvSwI2CWriteByte(a,b) drvSwI2CWriteByteFunc(b)
#define drvSwI2CReadByte(a,b) drvSwI2CReadByteFunc(b)
#endif


#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
#define drvSwI2CWriteBit(a, b) drvSwI2CWriteBitFunc(a, b)
#define drvSwI2CReadBit(a) drvSwI2CReadBitFunc(a)
#else
#define drvSwI2CWriteBit(a, b) drvSwI2CWriteBitFunc(b)
#define drvSwI2CReadBit(a) drvSwI2CReadBitFunc()
#endif

///////////////////////////////////////////////////////////////////////////////
// Local functions
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
static dosBool drvSwI2CWriteBitFunc(dosByte in_interface_index, dosByte in_bit);
static dosByte drvSwI2CReadBitFunc(dosByte in_interface_index);
#else
static dosBool drvSwI2CWriteBitFunc(dosByte in_bit);
static dosByte drvSwI2CReadBitFunc(void);
#endif

///////////////////////////////////////////////////////////////////////////////
//! Starts communication over I2C bus. This function sets start condition, sends
//! address, R/W bit and waits for ACK from the slave
//! \param I2C interface index (Only if drvSwI2C_MULTIPLE_INTERFACES_ENABLED is defined)
//! \param R/W mode flag
//! \param Chip (I2C) address
//! \return dosTrue - if start&address&mode acknowledged by the slave
dosBool drvSwI2CStartFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
												dosByte in_interface_index, 
#endif
												drvI2CRWMode in_mode, 
												dosByte in_address)
{
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
	drvSwI2CSettings* settings = &g_drvSwI2CSettings[in_interface_index];
#endif
	
	// check for collision
	if( drvSwI2CReadSDA() == 0 )
		return dosFalse;
	
	// bus start condition
	drvSwI2CClearSDA();
  drvSwI2CHalfBitDelay();
  drvSwI2CClearSCL();
  
 	// Transmit the address byte
	drvSwI2CWriteByte(in_interface_index, (in_address << 1) | ((dosByte)in_mode)); 
	
	// read ack
	return (drvSwI2CReadBit(in_interface_index) == 0);
}

///////////////////////////////////////////////////////////////////////////////
//! Restart communication over I2C bus. This function sets start condition, sends
//! address, R/W bit and waits for ACK from the slave
//! \param I2C interface index
//! \param R/W mode flag
//! \param Chip (I2C) address
//! \return dosTrue - if start&address&mode acknowledged by the slave
dosBool drvSwI2CRestartFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
												dosByte in_interface_index, 
#endif
												drvI2CRWMode in_mode, 
												dosByte in_address)
{
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
	drvSwI2CSettings* settings = &g_drvSwI2CSettings[in_interface_index];
#endif

	// set SDA to 1
	drvSwI2CReadSDA();
	drvSwI2CHalfBitDelay();
	
  // Clock stretching
	while (drvSwI2CReadSCL() == 0)
	{
		// Timeout should be added
	}
	
	return drvSwI2CStart(in_interface_index, in_mode, in_address);
}	

///////////////////////////////////////////////////////////////////////////////
//! Stops communication over I2C bus. This function sets bus stop condition
//! \param I2C interface index
void drvSwI2CStopFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
										dosByte in_interface_index
#else
										void
#endif
)
{
	#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
	drvSwI2CSettings* settings = &g_drvSwI2CSettings[in_interface_index];
	#endif

	// set SDA to 0
	drvSwI2CClearSDA();
  drvSwI2CHalfBitDelay();
  
	// Clock stretching
	while (drvSwI2CReadSCL() == 0)
	{
		// Timeout should be added
	}

	// SCL is high, set SDA from 0 to 1
	drvSwI2CReadSDA();
  drvSwI2CHalfBitDelay();
}

///////////////////////////////////////////////////////////////////////////////
// Sends data byte
dosBool drvSwI2CWriteByteFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
										dosByte in_interface_index,
#endif
										dosByte in_data)
{
	#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
	drvSwI2CSettings* settings = &g_drvSwI2CSettings[in_interface_index];
	#endif

	dosByte bit_index;
	dosBool success = dosTrue;
	dosBool ack = dosFalse;

	for (bit_index = 0; bit_index < 8 && success; bit_index++) 
	{
		success = drvSwI2CWriteBit(in_interface_index, (in_data & 0x80) != 0);

		in_data <<= 1;
	}
	
	// read ack
	if( success )
		ack = (drvSwI2CReadSDA() == 0);
	
	return ack;
}

///////////////////////////////////////////////////////////////////////////////
// Reads one byte from the bus
dosByte drvSwI2CReadByteFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
										dosByte in_interface_index,
#endif
										drvI2CACK in_ack)
{
	dosByte byte = 0;
	dosByte bit_index;
	
	for (bit_index = 0; bit_index < 8; bit_index++)
	{
		byte = (byte << 1) | drvSwI2CReadBit(in_interface_index);
	}
  
  drvSwI2CWriteBit(in_interface_index, (in_ack == drvI2C_ACK)?0:1);

	return byte;
}	

///////////////////////////////////////////////////////////////////////////////
//! Sends one bit over the I2C bus
//! \param I2C interface index
//! \param Bit to send
//! \return True if successfully sent
static dosBool drvSwI2CWriteBitFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
																			dosByte in_interface_index, 
#endif
																			dosByte in_bit)
{
	#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
	drvSwI2CSettings* settings = &g_drvSwI2CSettings[in_interface_index];
	#endif

	if (in_bit==0) 
		drvSwI2CReadSDA();
	else 
		drvSwI2CClearSDA();
		
  drvSwI2CHalfBitDelay();

	// Clock stretching
	while (drvSwI2CReadSCL() == 0)
	{
		// Timeout should be added
	}

	// SCL is high, now data is valid
	// If SDA is high, check that nobody else is driving SDA
	if (in_bit != 0 && drvSwI2CReadSDA() == 0) 
		return dosFalse;
		
  drvSwI2CHalfBitDelay();
	drvSwI2CClearSCL();
	
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
//! Reads one bit from the I2C bus
//! \param I2C interface index
//! \return Bit has been received from the bus
static dosByte drvSwI2CReadBitFunc(
#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
																		dosByte in_interface_index
#else
																		void
#endif
)
{
	#ifdef I2C_MULTIPLE_INTERFACES_ENABLED 
	drvSwI2CSettings* settings = &g_drvSwI2CSettings[in_interface_index];
	#endif
	
	dosByte received_bit;
	
	// Let the slave drive data
	drvSwI2CReadSDA();
  drvSwI2CHalfBitDelay();
  
	// Clock stretching
	while (drvSwI2CReadSCL() == 0)
	{
		// Timeout should be added
	}

	// SCL is high, now data is valid
	received_bit = (drvSwI2CReadSDA()==0)?0:1;
  drvSwI2CHalfBitDelay();
  drvSwI2CClearSCL();
  
	return received_bit;
}

